package pl.edu.agh.ssm.monitor.net;

import java.io.IOException; //import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;

import pl.edu.agh.ssm.monitor.listeners.PacketListener;
import pl.edu.agh.ssm.monitor.listeners.PacketListenersComparator;
import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.packet.Packet;

/**
 * Class responsible for receive packets from network card and pass them to
 * interested packet listeners. All listeners should first register in this
 * class to receive packets.
 * 
 * @author Tomasz Jadczyk
 * 
 */
public class PacketReceiver extends Thread implements jpcap.PacketReceiver {

	private NetworkInterface iface;
	private JpcapCaptor captor;
	private LinkedList<PacketListener> listenersQueue;
	private PriorityQueue<PacketListener> priorityQueue;
	private HashMap<PacketListener, PacketFilter> packetFilters;
	private HashMap<String, LinkedList<PacketListener>> listenersForSessions; //by sessionID
	private boolean promisc;

	public PacketReceiver(NetworkInterface iface, boolean promisc)
			throws IOException {
		this.iface = iface;
		setNetworkIface(iface, promisc);
		listenersQueue = new LinkedList<PacketListener>();
		priorityQueue = new PriorityQueue<PacketListener>(11,
				new PacketListenersComparator());
		packetFilters = new HashMap<PacketListener, PacketFilter>();
		listenersForSessions = new HashMap<String, LinkedList<PacketListener>>();
	}

	public void addPacketListener(PacketListener listener, PacketFilter filter) {
//		System.out.println("ADDDDD PAAAAAAAACKET listener: "
//				+ filter.getAddress() + ":" + filter.getHighPort() +
//				", on source: " + filter.getSourceAddress() + ":" + filter.getSourcePort());
		priorityQueue.add(listener);
		prepareOrderedListenersList();
		packetFilters.put(listener, filter);
	}
	
	public void addPacketListenerToSession(PacketListener listener, PacketFilter filter, String sessionID) {
		addPacketListener(listener, filter);
		LinkedList<PacketListener> listeners = listenersForSessions.get(sessionID);
		if(listeners == null) {
			listeners = new LinkedList<PacketListener>();
		}
		listeners.add(listener);
		listenersForSessions.put(sessionID, listeners);
	}
	
	public void removePacketListenersForSession(String sessionID) {
		LinkedList<PacketListener> listeners = listenersForSessions.get(sessionID);
		if(listeners != null) {
			for(PacketListener listener: listeners) {
				priorityQueue.remove(listener);
			}
			prepareOrderedListenersList();
		}
	}

	public void removePacketListener(PacketListener listener) {
		priorityQueue.remove(listener);
		prepareOrderedListenersList();
	}

	public List<PacketListener> findListeners(PacketFilter filter) {
		LinkedList<PacketListener> foundListeners = new LinkedList<PacketListener>();
		for (PacketListener l : listenersQueue) {
			if (packetFilters.get(l).equals(filter)) {
				foundListeners.add(l);
			}
		}
		return foundListeners;
	}

	public void setNetworkIface(NetworkInterface iface, boolean promisc)
			throws IOException {
		this.iface = iface;
		this.promisc = promisc;
	}

	@Override
	public void receivePacket(Packet packet) {
		pl.edu.agh.ssm.monitor.data.Packet msmPacket = new pl.edu.agh.ssm.monitor.data.Packet(
				packet);
		// create list of interested listeners for this packet
		LinkedList<PacketListener> listeners = new LinkedList<PacketListener>();
		for (PacketListener listener : listenersQueue) {
			/* Check if listener is interested on this packet */
			PacketFilter filter = packetFilters.get(listener);
			if (filter.matches(msmPacket.getDestPort(), msmPacket
					.getDestAddress(), msmPacket.getSourceAddress(), msmPacket
					.getSourcePort())) {
				/* Add listener to list */
				listeners.add(listener);
				/* Add all interested listeners on the same level */
				Iterator<PacketListener> iterator = listenersQueue.iterator();
				while (iterator.next() != listener)
					; // rewind iterator
				/* Check all listeners on the same level */
				PacketListener ls;
				while (iterator.hasNext()
						&& (ls = iterator.next()).getPriority() == listener
								.getPriority()) {
					PacketFilter filterTwo = packetFilters.get(ls);
					if (filterTwo.matches(msmPacket.getDestPort(), msmPacket
							.getDestAddress(), msmPacket.getSourceAddress(),
							msmPacket.getSourcePort())) {
						listeners.add(ls);
					}
				}
				break;
			}
		}

		passPacketToListeners(listeners, msmPacket);
	}

	private void passPacketToListeners(List<PacketListener> listeners,
			pl.edu.agh.ssm.monitor.data.Packet packet) {
		if (listeners != null) {
//			 System.out.println("Pass packet: FROM" + packet.getSourceAddress().getHostAddress() + ":" + packet.getSourcePort()
//					 + " TO: " + packet.getDestAddress().getHostAddress() + ":" + packet.getDestPort() + "   to listeners: " +
//			 listeners.size());
			for (PacketListener packetListener : listeners) {
				packetListener.processPacket(packet);
			}
		} // else {
		// System.out.println("Empty packet list");
		// }
	}

	private void prepareOrderedListenersList() {
		PriorityQueue<PacketListener> tmpQueue = new PriorityQueue<PacketListener>(
				11, new PacketListenersComparator());
		tmpQueue.addAll(priorityQueue);

		listenersQueue.clear();
		while (!tmpQueue.isEmpty()) {
			listenersQueue.add(tmpQueue.poll());
		}
	}

	public void startReceive() throws IOException {
		captor = JpcapCaptor.openDevice(this.iface, -1, promisc, 0);
		System.out.println("Device opened");
		captor.loopPacket(-1, this);
		// captor.processPacket(-1, this);
	}

	@Override
	public void run() {
		try {
			startReceive();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	@Override
	public void interrupt() {
		super.interrupt();
		captor.close();
	}
}
